
/* SortView is a custom view class that displays a graphically animating sort.  
 * Each sort creates a SortView to manage its display.  The SortView will draw 
 * the necessary animation when a sort is moving or comparing elements.
 *
 * Author: Julie Zelenski, NeXT Developer Support
 * You may freely copy, distribute and reuse the code in this example.  
 * NeXT disclaims any warranty of any kind, expressed or implied, as to 
 * its fitness for any particular use.
 */
 
 
#import "SortView.h"
#import "GenericSort.h"
#import "SortApp.h"
#import <appkit/Font.h>
#import <appkit/nextstd.h>
#import <dpsclient/wraps.h>
#import <math.h>
#import "SortWraps.h"

extern BOOL Abort;		// global variable to signal abort 


@implementation SortView:View


- initSort:aSort 
/* Each sort will create a new SortView for itself by calling initSort. 
 * This init method sets a default size view, sets up a few instance 
 * variables, grabs a few fonts and strings to be used later.  It also 
 * allocates a gState for itself to improve performance, because the 
 * sortviews are repeatedly lockFocus'ed on. 
 */
{      
#define HORIZ_MARGIN 3.0
#define VERT_MARGIN 13.0

    NXRect frameRect = {{2.0,2.0},{VIEW_WIDTH,VIEW_HEIGHT}};

    [self initFrame:&frameRect];
    sort = aSort;
    drawRect.size.width = bounds.size.width - 2*HORIZ_MARGIN;
    drawRect.size.height = bounds.size.height - VERT_MARGIN;
    drawRect.origin.x = bounds.origin.x + HORIZ_MARGIN;
    drawRect.origin.y = bounds.origin.y;
    numRows = 1;
    numPixelsInRow = drawRect.size.width;
    elementWidth = barWidth = 1.0;
    rowHeight = drawRect.size.height;
    numberFont = [Font newFont:"Helvetica" size:24 matrix:NX_IDENTITYMATRIX];
    titleFont = [Font newFont:"Helvetica" size:12 matrix:NX_IDENTITYMATRIX];
    [self setClipping:NO]; 	// I won't draw outside my bounds so don't clip
    [self allocateGState];   	// gstate will decrease lock/unlock focus time
    return self;
}    
   

- setUpForSize:(int)numElements;
/* This method is called by the sort when it knows how many elements are in 
 * the current data set.  It will calculates the rowHeight and barWidth 
 * that will allow all the elements to fit.  When there is a smaller data set,
 * the bars are wider and there is a gap between bars. With larger data sets, 
 * the sortView will layer the elements in more than one row 
 */
{
    numRows = 1;
    elementWidth = barWidth = floor(drawRect.size.width/numElements);
    switch (barWidth) {
        case 0: numRows = ceil((float)numElements/numPixelsInRow);
		barWidth = 1.0;   
		elementWidth = 1.0;
		break;
	case 1: break;
	case 2: barWidth -= 1.0; 
		break;
	default: barWidth -= 2.0;
		break;
    }
    rowHeight = floor(drawRect.size.height/(float)numRows);
    return self;
}


- compare:(int)element1 value:(int)value1 with:(int)element2 value:(int)value2
/* This method is called to show two values are being compared.  The 
 * comparison is shown by highlighting the two bars in light gray.  This 
 * highlighting is done with instance drawing, because I want it to be 
 * transitory.  At each drawing request, the sortView will hide any previous 
 * instance drawing.  This way I never have to erase the comparisons. They 
 * will be "flushed" away in the next drawing.
 * PSWcompareRects is a wrap which simply turns on instance drawing, sets 
 * the gray, fills the two rectangles in gray and turns off instance drawing.
 */
{  
    NXPoint pt1,pt2;

    [self lockFocus];
    pt1.x = drawRect.origin.x + (element1%numPixelsInRow)*elementWidth;
    pt1.y = (numRows - (element1/numPixelsInRow)-1)*rowHeight;
    pt2.x = drawRect.origin.x + (element2%numPixelsInRow)*elementWidth;
    pt2.y = (numRows - (element2/numPixelsInRow)-1)*rowHeight;
    PShideinstance(0.0,0.0,bounds.size.width,bounds.size.height);
    PSWcompareRects(pt1.x,pt1.y,barWidth,value1,pt2.x,pt2.y,value2);
    [self unlockFocus];
    return self;
}


 - swap:(int)element1 value:(int)value1 with:(int)element2 value:(int)value2
/* This method is called to swap two elements.  It draws only the changed
 * parts:  it adds height to the smaller bar (by drawing in black) and removes
 * height from the larger bar (by drawing in white). 
 * PSWswapRects is a wrap which sets the gray to white, fills the white 
 * rectangle, sets the gray to black, fills the black rectangle, and flushes
 * the drawing to the screen immediately.
 */
{  
    NXPoint pt1,pt2;

    if ((value1 != value2) && !Abort) { // if values actually need to change
        [self lockFocus];
        pt1.x = drawRect.origin.x + (element1%numPixelsInRow)*elementWidth;
        pt1.y = value1 + (numRows - (element1/numPixelsInRow)-1)*rowHeight;
        pt2.x = drawRect.origin.x + (element2%numPixelsInRow)*elementWidth;
        pt2.y = value1 + (numRows - (element2/numPixelsInRow)-1)*rowHeight;
        PShideinstance(0.0,0.0,bounds.size.width,bounds.size.height);
        PSWswapRects(pt1.x,pt1.y,pt2.x,pt2.y,barWidth,value2-value1);
        [self unlockFocus];
    }
    return self;
}


- moveValue:(int)new to:(int)element oldValue:(int)old
/* This method is called to change the value of an element. It draws only the 
 * changed part:  it adds height (draws in black) if the value increased, or
 * it removes height (draws in white) if the value decreased.
 * PSWmoveRects is a wrap which sets the gray appropriately, fills the 
 * rectangle, and flushes the drawing to the screen immediately.
 */
{
    NXPoint p;
    
    if ((new != old) && !Abort) {	// if value actually needs to change
        [self lockFocus];
        p.x = drawRect.origin.x + (element%numPixelsInRow)*elementWidth;
        p.y = MIN(old,new) + (numRows - (element/numPixelsInRow)-1)*rowHeight; 
        PShideinstance(0.0,0.0,bounds.size.width,bounds.size.height);
	PSWmoveRect(p.x,p.y,barWidth,ABS(old-new),((new>old)?NX_BLACK:NX_WHITE));
        [self unlockFocus];
    }
    return self;
}


- drawSelf:(const NXRect *)rects :(int)rectCount;  
/* In drawSelf, the sortView simple erases its background, draws the border and
 * labels itself with the appropriate name.
 */
{         
    NXEraseRect(&bounds);       
    PSWdrawBorder(bounds.size.width,bounds.size.height);
    [titleFont set];
    PSWdrawName(drawRect.origin.x,drawRect.size.height,(char *)[sort getName]);
    return self;
}
 

- drawStatistics;
/* This methods displays the statistics when a sort finishes.  It fades the 
 * sort background, and queries the sort for the necessary numbers to display 
 * and title.  It looks up the strings for the titles in the SortApp's 
 * NXStringTable.
 */
{    
   char a[9],b[9],c[9],d[9];
   id sTable;
   
    sprintf(a,"%8d",[sort totalTicks]);
    sprintf(b,"%8d",[sort compares]);
    sprintf(c,"%8d",[sort moves]);
    sprintf(d,"%8d",[sort fcalls]);
    [numberFont set];
    PSWdrawStrings(a,b,c,d,35.0);
    [titleFont set];
    if (!tickString) {
        sTable = [NXApp stringTable];
        tickString = NXCopyStringBuffer([sTable valueForStringKey:"Ticks"]);
        moveString = NXCopyStringBuffer([sTable valueForStringKey:"Moves"]);
        compString = NXCopyStringBuffer([sTable valueForStringKey:"Compares"]);
        fcallString = NXCopyStringBuffer([sTable valueForStringKey:"Fcalls"]);
    }
    PSWdrawStrings(tickString, compString, moveString, fcallString, 65.0);
    return self;
}

- displayFinished;
/* This methods displays a sort when it finishes.  It fades the sort
 * background, and if the sort finished normally (i.e. it wasn't canceled),
 * it will call a method to display the sort's statistics.
 */
{
    [self lockFocus];
    PSWfade(bounds.size.width,bounds.size.height);
    PSWdrawBorder(bounds.size.width,bounds.size.height);
    [titleFont set];
    PSWdrawName(drawRect.origin.x,drawRect.size.height,(char *)[sort getName]);
    if (!Abort) 
        [self drawStatistics];
    [self unlockFocus];
    [window flushWindow];
    return self;
}

   
@end